<?php
// **************************************************
// PHP Framework by ElmueSoft (www.netcult.ch/elmue)
// **************************************************

class Magick
{
    protected $Wand;
    protected $Pixel;
    protected $Draw;

    public function __construct()
    {
        if (!extension_loaded("magickwand"))
            throw new Exception("The MagickWand extension must be installed!");

        $this->Wand = NewMagickWand();
    }

    public function __destruct()
    {
        $this->DestroyMagick();
        $this->DestroyPixel();
        $this->DestroyDraw();
    }
    
    public function DestroyMagick()
    {
    	if (!empty($this->Wand)) DestroyMagickWand($this->Wand);
    	$this->Wand = NULL;
    }
    public function DestroyPixel()
    {
    	if (!empty($this->Pixel)) DestroyPixelWand($this->Pixel);
    	$this->Pixel = NULL;
    }
    public function DestroyDraw()
    {
    	if (!empty($this->Draw)) DestroyDrawingWand($this->Draw);
    	$this->Draw = NULL;
    }

    // ---------- Private -----------
    
    private function CheckError()
    {
    	$ErrMsg = MagickGetExceptionString($this->Wand);

    	if (empty($ErrMsg) && !empty($this->Pixel))
    	{
    		$ErrMsg = PixelGetExceptionString($this->Pixel);
    	}
    	
    	if (empty($ErrMsg) && !empty($this->Draw))
    	{
    		$ErrMsg = DrawGetExceptionString($this->Draw);
    	}
    	
    	if (empty($ErrMsg))
        	$ErrMsg = "Magick Exception without Explanation.";
    		
    	throw new Exception($ErrMsg);
    }
    
	// ---------- Static ------------

	// Get the image size without reading a huge image into memory
    public static function GetImageInfo($File, &$Width, &$Height, &$Type, &$TypeStr)
    {
    	$Wand = NewMagickWand();

    	if (!MagickPingImage($Wand, $File))
    		return false;

    	$Width  = MagickGetImageWidth ($Wand);
    	$Height = MagickGetImageHeight($Wand);
    	$Type   = MagickGetImageType  ($Wand);
    	
    	switch ($Type)
    	{
	    	case MW_PaletteType:              $TypeStr = "Palette";              break; // GIF
	    	case MW_PaletteMatteType:         $TypeStr = "PaletteMatte";         break; // Transparent GIF
	    	case MW_GrayscaleType:            $TypeStr = "Grayscale";            break; // GIF
	    	case MW_GrayscaleMatteType:       $TypeStr = "GrayscaleMatte";       break; // Transparent GIF
	    	case MW_TrueColorType:            $TypeStr = "TrueColor";            break; // JPG, PNG
	    	case MW_TrueColorMatteType:       $TypeStr = "TrueColorMatte";       break; // Transparent PNG
	    	case MW_ColorSeparationType:      $TypeStr = "ColorSeparation";      break;
	    	case MW_ColorSeparationMatteType: $TypeStr = "ColorSeparationMatte"; break;
	    	case MW_BilevelType:              $TypeStr = "Bilevel";              break;
	    	case MW_OptimizeType:             $TypeStr = "Optimize";             break;
	    	default:                          $TypeStr = "Undefined";            break;
    	}

		DestroyMagickWand($Wand);
    	return true;
    }

	// ---------- Member ------------

    // Loads an image from a file
    // If the image is transparent a unicolor background is painted behind the image
    public function LoadFromFile($File, $BackCol="#CCCCCC")
    {
        if (!MagickReadImage($this->Wand, $File))
        {
	    	$ErrMsg = MagickGetExceptionString($this->Wand);
	    	
	    	if (empty($ErrMsg)) $ErrMsg = "MagickWand Image Load Error: '$File'";
	    	
   			throw new Exception($ErrMsg);
        }
        
        $Type = MagickGetImageType($this->Wand);
        switch ($Type)
        {
    	case MW_PaletteMatteType:   // Transparent GIF
    	case MW_GrayscaleMatteType: // Transparent GIF
    	case MW_TrueColorMatteType: // Transparent PNG
    	case MW_ColorSeparationMatteType:
    	{
    	    // Transparent images must be painted over a defined background color
	        // otherwise they appear over black or mixed background.
            $NewWand = NewMagickWand();
            if (!MagickNewImage($NewWand, $this->Width(), $this->Height(), $BackCol))
            	$this->CheckError();

            if (!MagickCompositeImage($NewWand, $this->Wand, MW_OverCompositeOp, 0, 0))
            	$this->CheckError();

            $this->DestroyMagick();
            $this->Wand = $NewWand;
			break;
    	}       	        	        	
    	case MW_TrueColorType:        break; // JPG, PNG
    	case MW_GrayscaleType:        break; // GIF
    	case MW_PaletteType:          break; // GIF
    	case MW_ColorSeparationType:  break;
    	case MW_BilevelType:          break;
    	case MW_OptimizeType:         break;
    	case MW_UndefinedType:
    	default: 
    		throw new Exception("Unrecognized Image Type: $File");
        }
    }

    public function Width()
    {
        return MagickGetImageWidth($this->Wand);
    }
    public function Height()
    {
        return MagickGetImageHeight($this->Wand);
    }
    
    public function Rotate($Angle, $BackCol="#FFFFFF")
    {
        if ($Angle == 0)
            return;

        $this->Pixel = NewPixelWand($BackCol);
        
        if (!MagickRotateImage($this->Wand, $this->Pixel, $Angle))
        	$this->CheckError();
        	
        $this->DestroyPixel();
    }

    public function ResizeBy($Factor)
    {
        if ($Factor == 1)
            return;

        $this->ResizeTo($this->Width() * $Factor, $this->Height() * $Factor);
    }

    public function ResizeTo($Width, $Height)
    {
    	// BoxFilter is buggy
        if (!MagickResizeImage($this->Wand, $Width, $Height, MW_HanningFilter, 0.7))
        	$this->CheckError();
    }

    public function Crop($Left, $Top, $Width, $Height)
    {
        if (!MagickCropImage($this->Wand, $Width, $Height, $Left, $Top))
        	$this->CheckError();
    }
    
    public function Flip()
    {
    	return MagickFlipImage($this->Wand);
    }
    public function Mirror()
    {
    	return MagickFlopImage($this->Wand);
    }
    
    public function Blur($Factor)
    {
    	if (!MagickBlurImage($this->Wand, 0, $Factor))
    		$this->CheckError();
    }
    
    // Adds thext to the image
    // Possible Gravity values: MW_ForgetGravity,MW_NorthWestGravity,MW_NorthGravity,MW_NorthEastGravity,MW_WestGravity,
    // MW_CenterGravity,MW_EastGravity,MW_SouthWestGravity,MW_SouthGravity,MW_SouthEastGravity,MW_StaticGravity
    public function WriteText($Text, $Color, $FontFile, $FontSize, $PosX, $PosY, $Gravity, $Angle, $Transparency)
    {
    	$this->Draw  = NewDrawingWand();
		$this->Pixel = NewPixelWand($Color);
		
		if (!is_file($FontFile))
			throw new Exception("Font File does not exist: $FontFile");
		    	
		if (!DrawSetFont($this->Draw, $FontFile))
			$this->CheckError();
			
		DrawSetFontSize($this->Draw, $FontSize);
		DrawSetGravity ($this->Draw, $Gravity);
		
		PixelSetOpacity ($this->Pixel, $Transparency);
		DrawSetFillColor($this->Draw,  $this->Pixel);
		
		// Workaround for bug if position is not integer -> floor()
		if (!MagickAnnotateImage($this->Wand, $this->Draw, round($PosX), round($PosY), round($Angle), $Text))
			$this->CheckError();
		
		$this->DestroyPixel();
		$this->DestroyDraw();
    }
    
    // Streams the image to the browser
    public function StreamOut($Type, $JpegQuality=85)
    {
        switch ($Type)
        {
        case "gif":
            header('Content-Type: image/gif');

            if (!MagickSetFormat($this->Wand, 'GIF') ||
            	!MagickEchoImageBlob($this->Wand))
            		$this->CheckError();
            exit;

        case "png":
            header('Content-Type: image/x-png');

            if (!MagickSetFormat($this->Wand, 'PNG') ||
            	!MagickEchoImageBlob($this->Wand))
            		$this->CheckError();
            exit;

        case "jpg":
            header('Content-Type: image/jpg');

            if (!MagickSetFormat($this->Wand, 'JPG') ||
            	!MagickSetImageCompression($this->Wand, MW_JPEGCompression)  ||
            	!MagickSetImageCompressionQuality($this->Wand, $JpegQuality) ||
            	!MagickEchoImageBlob($this->Wand))
            		$this->CheckError();
            exit;

        default:
            throw new Exception("Unsupported image type: $Type");
        }
    }

    // Saves the image to file
    public function Save($File, $JpegQuality)
    {
        $Ext = $this->GetFileExtension($File);
        switch (strtolower($Ext))
        {
        case "gif":
            if (!MagickSetFormat ($this->Wand, 'GIF') ||
            	!MagickWriteImage($this->Wand, $File))
            		$this->CheckError();
            break;

        case "png":
            if (!MagickSetFormat ($this->Wand, 'PNG') ||
            	!MagickWriteImage($this->Wand, $File))
            		$this->CheckError();
            break;

        case "jpg":
            if (!MagickSetFormat($this->Wand, 'JPG') ||
            	!MagickSetImageCompression($this->Wand, MW_JPEGCompression)  ||
            	!MagickSetImageCompressionQuality($this->Wand, $JpegQuality) ||
            	!MagickWriteImage($this->Wand, $File))
            		$this->CheckError();
            break;

        default:
            throw new Exception("Unsupported image type: $Ext");
        }
    }

	public function GetFileExtension($Path)
	{
		$Split = pathinfo($Path);
		return strtolower($Split["extension"]);
	}
    
    // ------------------------------ HIGHER LEVEL FUNCTIONS -----------------------------------------

	// returns a Captcha string for use in CreateCaptchaImage()
   	// This string avoids similar characters like "l1I" or "oO0D" or "b6" or "qg9" or "Z2" or "8B" or "sS5"
   	// which would be difficult to distinguish on some fonts
   	// ATTENTION:
   	// Always verify the Captcha string case-INsensitive because characters are printed with random size
   	// so a lowecase "x" may look identical as an uppercase "X".
   	// Additionally some fonts (like Angelus) show only uppercase letters!
    public static function CreateCaptchaCode()
    {
		$CaptchaCode = "ACEFGHJKMNPRTUVWXYacdefhikmnprtuvwxy347";
		$CaptchaCode = str_shuffle($CaptchaCode);
		return substr($CaptchaCode, 0, 5);
    }
    
    // Creates an image with a background and 5 characters which is not machine-readable
    // Each random character is written with a random angle at a random position
    public function CreateCaptchaImage($RandomStr)
    {
    	$StrLen = 5; // These values must be adapted to the size of the background image
    	$PosX   = 15;
    	$DistX  = 70;
    	    	
    	if (strlen($RandomStr) != $StrLen)
    		throw new Exception("The string must have $StrLen characters!");
    		
		// This background image has 370 x 130 pixels    		
    	$this->LoadFromFile("CaptchaBackg.gif");
    	
	   	for ($i=0; $i<$StrLen; $i++)
    	{
    		$Angle = mt_rand(-30, 30);
    		$PosY  = mt_rand(-17, 17); // These values must be adapted to the size of the background image
    		$Size  = mt_rand( 70, 90);
    		
    		// MW_WestGravity writes hor left aligned, vert centered
    		$this->WriteText($RandomStr[$i], "#808080", "Angelus.ttf", $Size, $PosX, $PosY, MW_WestGravity, $Angle, 0);
    		
    		$PosX += $DistX;
    	}
    	
    	// Make the lines of font and background look exactly identical
    	$this->Blur(0.5);
    }
}
?>